/* A COMPLETER - Pef, 28 avril
       Di-Costanzo Jonathan
       Fauché Pierre-Élie
       3TC 3
*/


/* Copyright (c) 2004 by Dept TC INSA Lyon.  All Rights Reserved */

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#include "p2p_common.h"
#include "p2p_options.h"
#include "p2p_msg.h"
#include "p2p_topology.h"
#include "p2p_network.h"
#include "p2p_research.h"
#include "p2p_share.h"
#include "p2p_ui.h"

#define MAX(a,b)    (((a)>(b))?(a):(b))

/*******************************************************************
Remplit le server_params avec les valeurs passées en arguments,
alloue et remplit les adresses neighbors sur soi-même
********************************************************************/
int
initialisation(int argc, char* argv[], server_params *sp)
{
	char *pch;
	char ports[12];
	char *adresse;
 
  // répertoire
  pch = strtok (argv[1],"=");
  pch = strtok (NULL, "=");
  sp->dir_name=pch;
  // nom server
  pch = strtok (argv[3],"=");
  pch = strtok (NULL, "=");
  sp->server_name=pch;
  // ui tcp port
  pch = strtok (argv[4],"=");
  pch = strtok (NULL, "=");
  sp->port_ui=atoi(pch);
  // p2p tcp port
  pch = strtok (argv[5],"=");
  pch = strtok (NULL, "=");
  sp->port_p2p_tcp=atoi(pch);
  // p2p udp port
  pch = strtok (argv[6],"=");
  pch = strtok (NULL, "=");
  sp->port_p2p_udp=atoi(pch);
  // verbosity
  pch = strtok (argv[7],"=");
  pch = strtok (NULL, "=");
  sp->verbosity=atoi(pch);
  // adresse ip complète avec les ports
  pch = strtok (argv[2],"=");
  pch = strtok (NULL, "=");
  adresse=pch;
  sprintf(ports,":%d:%d",sp->port_p2p_tcp,sp->port_p2p_udp);
  strncat(adresse,ports,strlen(ports));
  // création de l'adresse et complétion
  sp->p2pMyId=p2p_addr_create();
  if ( p2p_addr_setstr(sp->p2pMyId, adresse) )
		return(P2P_ERROR);
 
  // On met ses 2 voisins sur soi-même
  sp->neighbors.left=p2p_addr_create();
  p2p_addr_copy(sp->neighbors.left, sp->p2pMyId);
  sp->neighbors.right=p2p_addr_create();
  p2p_addr_copy(sp->neighbors.right, sp->p2pMyId);
 
  //on crée les structures de listes
  sp->p2pSearchFaits = p2p_search_create();
  sp->p2pSearchVus   = p2p_search_create();
  sp->p2pResultats   = p2p_reply_create();
  //initialisation de la structure network
  sp->network.right_end=NULL;
  sp->network.left_end=NULL;
  sp->network.reference=NULL;
  sp->network.lonesome_nodes=NULL;
  
  //on dit que l'on a aucun client connecté
  sp->client_ui=0;
  sp->gui=P2P_ERROR;
  sp->get_faits=-1;
  
  //affichage de départ sur le client
  VERBOSE(sp,CLIENT,"\n");
  VERBOSE(sp,CLIENT,"Starting p2p server node :\n");
  VERBOSE(sp,CLIENT,"  pid               = %d\n",getpid());
  VERBOSE(sp,CLIENT,"  connect          = \"(null)\"\n");
  VERBOSE(sp,CLIENT,"  dir_name          = \"%s\"\n",sp->dir_name);
  VERBOSE(sp,CLIENT,"  server_name       = \"%s\"\n",sp->server_name);
  VERBOSE(sp,CLIENT,"  listening ip      = \"%s\"\n",p2p_addr_get_ip_str(sp->p2pMyId));
  VERBOSE(sp,CLIENT,"  ui tcp            = %d\n"    ,sp->port_ui);
  VERBOSE(sp,CLIENT,"  p2p tcp           = %d\n"    ,sp->port_p2p_tcp);
  VERBOSE(sp,CLIENT,"  p2p udp           = %d\n"    ,sp->port_p2p_udp);
  VERBOSE(sp,CLIENT,"  verbose           = %d\n"    ,sp->verbosity);
  
  return(P2P_OK);
}

/*******************************************************************
Pour se raccorder automatiquement au réseau si on lance plusieurs 
noeuds en même temps
********************************************************************/
int
raccordementPrimaire(server_params *sp, char *txtNoeud) {
	char *pch;
	pch = strtok (txtNoeud,"=");
  pch = strtok (NULL, "=");
	
	p2p_addr noeud=p2p_addr_create();
	if (p2p_addr_setstr(noeud,pch) != P2P_OK) {
  	fprintf(stderr,"raccordement primaire: could not parse p2p address\n");
  } else {
  	p2p_join_req(sp, noeud);
  }
	
	p2p_addr_delete(noeud);
	
	return(P2P_OK);
}

/*******************************************************************
Fonction principale
********************************************************************/
int
main(int argc, char* argv[])
{
  int s_ui, s_ui2=-1, s_tcp, s_udp, s; // sockets utilisées
  fd_set rfds; // contient les sockets en lecture : Read File Descriptor Set
  struct timeval tv;
  int retval, retour=P2P_OK;
  int n, attenteMax;
  struct sockaddr_in adresse;
  unsigned int lg=sizeof(adresse);
  server_params sp;
  p2p_msg msg;
 
  // On remplit le sp avec les arguments
  if ( initialisation(argc,argv,&sp) )
		exit(0);
		
	// Attends jusqu'à 120 minutes.
  attenteMax=120; //attente du select en minutes
  tv.tv_sec = attenteMax*60;
  tv.tv_usec = 0;
  VERBOSE(&sp,CLIENT,"this node is stopped after %d minutes\n\n",attenteMax);

  //ouvrir les ports du serveur en SOCK_DGRAM (UDP) ou en SOCK_STREAM (TCP)
  // UI en TCP
  s_ui=creer_socket (SOCK_STREAM, sp.port_ui);
  if (listen(s_ui, 10) == -1) {
    VERBOSE(&sp,VPROTO,"unable to create ui socket\n");
    return P2P_ERROR;
  }
  VERBOSE(&sp,VSINFO,"ui socket listening : ok\n");
 
  // TCP
  s_tcp=creer_socket (SOCK_STREAM, sp.port_p2p_tcp);
  if (listen(s_tcp, 10) == -1) {
    VERBOSE(&sp,VPROTO,"unable to create p2p tcp socket\n");
    return P2P_ERROR;
  }
  VERBOSE(&sp,VSINFO,"p2p tcp socket listening : ok\n");
 
  // UDP
  s_udp=creer_socket(SOCK_DGRAM, sp.port_p2p_udp);
  VERBOSE(&sp,VSINFO,"p2p udp socket listening : ok\n\n");
 
	//raccodement à l'anneau si plusieurs noeuds lancés en même temps
 	if (argc>8) {
 		raccordementPrimaire(&sp,argv[8]);
	}
       
  while (1) {
       	
  	//on met le set à zéro
  	FD_ZERO(&rfds);
  	//on remplit le set avec les descripteurs
  	if (s_ui2!=-1) FD_SET(s_ui2,&rfds); 	
  	FD_SET(s_tcp,&rfds);
  	FD_SET(s_ui,&rfds);
  	FD_SET(s_udp,&rfds);  
  	n=MAX(MAX(MAX(s_ui,s_ui2),s_tcp),s_udp)+1;
 
  	retval = select(n, &rfds, NULL, NULL, &tv);
  	VERBOSE(&sp,VMCTNT,"select() returns with %d file descriptors ready\n",retval);
  
  	if (FD_ISSET(s_ui, &rfds)) {
    
  	  //avant de connecter le client on test si un autre client n'est pas déjà relié au seveur.
  	  if (sp.client_ui == 0 || sp.client_ui == -1){ //0 -> premiere connection 
  	  																							//-1 -> un client vient de quitter
  	  																							//donc le serveur est libre
  	  
  	  	VERBOSE(&sp,VMCTNT,"ui : reading listening socket\n");
  	  	// créer socket s et écouter et renseigner les server params
  	  	s_ui2=accept(s_ui,(struct sockaddr*) &adresse, &lg);
    
  	  	if (s_ui2==-1) {
  	  		VERBOSE(&sp, VPROTO,"cannot accept the connection");
  	  		break;
  	  	}
  	  
  	  	VERBOSE(&sp,VMCTNT,"ui : new connection accepted\n\n");
    
  	  	sp.client_ui=s_ui2;
    	
  	  	VERBOSE(&sp,CLIENT,"\n** connection au noeud %s\n",sp.server_name);
  	  	VERBOSE(&sp,CLIENT,"** p2p adresse -->  %s\n",p2p_addr_get_str(sp.p2pMyId));
  	  	VERBOSE(&sp,CLIENT,"\n%s:: ",sp.server_name);
    	
    
 	   } else {//si c'est un nouveau client
  	  
  	  //on sauvegarde la socket d'écoute du client_actuel
  	  int vrai_client = s_ui2;
  	  
  	  //on accepte la connection pour pouvoir lui envoyé un message d'erreur
  	  s_ui2=accept(s_ui,(struct sockaddr*) &adresse, &lg);
  	  char* message = "\n\n/!\\ sorry someone was faster than you /!\\\n\n\0";
      write(s_ui2, message, strlen(message));
    
      //clin d'oeil au client
      VERBOSE(&sp,CLIENT,"\n\n/!\\ someone tried to take your place but didn't succeed /!\\\n\n");
      VERBOSE(&sp,CLIENT,"\n%s:: ",sp.server_name);
     	
     	//on blablatte sur le serveur puis on lui réindique la socket d'écoute
     	VERBOSE(&sp,VMCTNT,"a new client tried to have a connection... but missed ^^\n");
     	s_ui2 = vrai_client;     
     }
                
 	  } else if (FD_ISSET(s_tcp, &rfds)) {
 	     VERBOSE(&sp,VMCTNT,"incoming tcp traffic\n");
 	     s=accept(s_tcp,(struct sockaddr*) &adresse, &lg);
 	     msg=p2p_msg_create();
 	     p2p_tcp_msg_recvfd(&sp, msg, s);
 	     // traiter le message reçu
 	     switch (p2p_msg_get_type(msg)) {
 	       case P2P_MSG_JOIN_REQ :    retour=p2p_join_asked(&sp, msg, s);   break;
 	       case P2P_MSG_LINK_UPDATE : retour=p2p_update_received(&sp, msg); break;
 	       case P2P_MSG_GET :         retour=p2p_get_received(&sp, msg, s); break;
 	     }
	      close(s);
	      p2p_msg_delete(msg);
	               
	   } else if (FD_ISSET(s_udp, &rfds)) {
	      VERBOSE(&sp,VMCTNT,"incoming udp traffic\n");
	      msg=p2p_msg_create();
	      p2p_udp_msg_recvfd(&sp, msg, s_udp);
	      // traiter le message
	      switch (p2p_msg_get_type(msg)) {
	        case P2P_MSG_SEARCH :         retour=p2p_search_received(&sp, msg);         break;
	        case P2P_MSG_REPLY :          retour=p2p_reply_received(&sp, msg);          break;
	        case P2P_MSG_NEIGHBORS_REQ :  retour=p2p_neighbors_req_received(&sp, msg);  break;
	        case P2P_MSG_NEIGHBORS_LIST : retour=p2p_neighbors_list_received(&sp, msg); break;
	      }
	      p2p_msg_delete(msg);
	               
	   } else if (FD_ISSET(s_ui2, &rfds)) {
	      VERBOSE(&sp,VMCTNT,"incoming tcp_ui traffic\n");
	      if (ui_command(&sp)==P2P_ERROR) {
	        break;
	      }
	     s_ui2=sp.client_ui; //si l'ui a fait quit, on met à jour la socket
	     VERBOSE(&sp,CLIENT,"%s:: ",sp.server_name);
	               
	   } else break; //aucune socket en activité depuis 120 minutes
       
  	  //if (retour!=P2P_OK) break;
       
 	 }// fin while(1)


  // Suppression de la structure sp
  sp_delete(&sp);
  close(s_ui); close(s_tcp); close(s_udp); close(s_ui2);
 
  return 0;
}

